/*
 * Jamus
 * Copyright (C) 2010 Jamus Team
 *
 * Jamus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Jamus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Jamus.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef __FRONTEND_MPD_CONNECTION
#define __FRONTEND_MPD_CONNECTION

#include <boost/asio.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <log4cxx/logger.h>

#include "../../backend/Plugin.h"

namespace frontend {
namespace mpd {

class Plugin;

/**
 * Upon accepting a connection, the MPD front-end creates a shared_ptr on a
 * new Connection object. Then, start() is called and the shared_ptr is thrown
 * away. As long as there are no errors, the Connection object will take care
 * of always having a shared_ptr of itself around (by enqueuing async requests
 * on the io_service). Once an error occurs, no async requests will be enqueued
 * anymore, thus leaving no shared_ptr on the object. The object gets deleted 
 * once it is no longer needed.
 *
 * To summarize, create a new Connection using a shared_ptr. Once you call
 * start(), you can throw the shared_ptr away and rely on Connection that it
 * will delete itself when done.
 */
class Connection : public boost::enable_shared_from_this<Connection>, private boost::noncopyable
{
public:
	/**
	 * Creates a new Connection object using the specified io_service to
	 * enqueue its async requests.
	 */
	Connection(boost::asio::io_service&, backend::Plugin&, frontend::mpd::Plugin&);
	~Connection();

	boost::asio::ip::tcp::socket& getSocket() { return socket; }

	/**
	 * Enqueues asynchronous socket read / writes operations on the io_service.
	 * Does not block.
	 */
	void start();

	// Gets called when the back-end notices the playlist has changed
	void onPlaylistChanged();
	void onPlaybackStatusChanged();
	void onVolumeChanged();

private:
	void read();
	void write();

	void onWrite(const boost::system::error_code&, std::size_t);
	void onRead(const boost::system::error_code&, std::size_t);

	//! Returns the information about a track
	std::string trackInfo(const Track&) const;
	//! Like trackInfo but also returns stuff like playlist position
	std::string trackInPlaylistInfo(const backend::Plugin::Playlist&, int index) const;

	/**
	 * Returns the string that informs the client about changes while in idle
	 * mode (without "OK"). Also resets the flags of changes and isIdle.
	 */
	std::string endIdle();

	/**
	 * If the client is idle, notifies him of all idleEvents that occured (see
	 * below). Otherwise, does nothing.
	 * Precondition: mutex is locked!
	 */
	void notifyIfIdle();

	/**
	 * Executes the specified request and returns the response.
	 * "OK" will be appended to the return value unless an exception is
	 * thrown. So, if handling failed, an exception should be thrown so an
	 * "ACK" can be appended instead of an "OK". Likewise, if nothing should
	 * be returned, you must throw an exception.
	 *
	 * @throws IllegalCommandException if an unknown command was specified
	 * @throws IllegalArgumentException if the command was supplied with
	 * unexpected arguments or not supplied with expected ones.
	 * @throws CloseConnectionException if the request is to close the
	 * connection. In this case, no ACK should be sent since this is not an
	 * error.
	 * @throws DoNotReplyException if the request was handled successfully but no
	 * response should be sent.
	 */
	std::string handleRequest(const std::string& request);

	// Map handler names to respective handler methods
	typedef std::vector<std::string> HandlerArgs;
	typedef std::string (Connection::*HandlerPtr)(const HandlerArgs&);
	typedef std::map<std::string, HandlerPtr> HandlerMap;
	static HandlerMap handlers;

	// Specific request handlers
	std::string add(const HandlerArgs& args);
	std::string commands(const HandlerArgs& args);
	std::string clear(const HandlerArgs& args);
	std::string close(const HandlerArgs& args);
	std::string currentsong(const HandlerArgs& args);
	std::string delete_(const HandlerArgs& args);
	std::string find(const HandlerArgs& args);
	std::string idle(const HandlerArgs& args);
	std::string list(const HandlerArgs& args);
	std::string listplaylistinfo(const HandlerArgs& args);
	std::string lsinfo(const HandlerArgs& args);
	std::string next(const HandlerArgs& args);
	std::string noidle(const HandlerArgs& args);
	std::string notcommands(const HandlerArgs& args);
	std::string outputs(const HandlerArgs& args);
	std::string pause(const HandlerArgs& args);
	std::string play(const HandlerArgs& args);
	std::string playid(const HandlerArgs& args);
	std::string playlistid(const HandlerArgs& args);
	std::string playlistinfo(const HandlerArgs& args);
	std::string plchanges(const HandlerArgs& args);
	std::string previous(const HandlerArgs& args);
	std::string seek(const HandlerArgs& args);
	std::string seekid(const HandlerArgs& args);
	std::string setvol(const HandlerArgs& args);
	std::string stats(const HandlerArgs& args);
	std::string status(const HandlerArgs& args);
	std::string stop(const HandlerArgs& args);
	std::string tagtypes(const HandlerArgs& args);

	static log4cxx::LoggerPtr logger;

	frontend::mpd::Plugin& frontend;
	backend::Plugin& backend;

	boost::asio::ip::tcp::socket socket;

	// Stuff for informing the user of events... XXX: Have to document better!
	boost::mutex mutex;
	bool retryIfAborted;

	// Use a strand so async handlers never get called concurrently
	boost::asio::io_service::strand strand;

	std::string writeBuf;
	boost::asio::streambuf readBuf;

	bool commandListActive;
	std::vector<std::string> commandList;

	bool isIdle;

	// Events that the user is notified of when in idle mode
	enum IdleEvent {
		CHANGED_PLAYLIST        = 1,
		CHANGED_PLAYBACK_STATUS = 1 << 1,
		CHANGED_MIXER           = 1 << 2
	};
	int idleEvents; // Bitmask (ORed IdleEvents)
};

typedef boost::shared_ptr<Connection> ConnectionPtr;

} // namespace mpd
} // namespace frontend

#endif
